package it.eng.area118.mdocommon.android.ebus;

import it.eng.area118.mdocommon.android.log.BasicLogger;
import it.eng.area118.mdocommon.android.util.ConfigurationConstants;
import it.eng.area118.mdocommon.android.util.SharedPreferenceUtil;
import it.eng.area118.mdocommon.ebus.EBusMessage;
import it.eng.area118.mdocommon.ebus.EBusMessageHandler;
import it.eng.area118.mdocommon.ebus.EBusServiceState;
import it.eng.area118.mdocommon.ebus.EBusServiceStateListener;
import it.eng.area118.mdocommon.ebus.EventBusService;
import it.eng.area118.mdocommon.ebus.MessageConverter;

import java.io.Serializable;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Message.Type;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.muc.MultiUserChat;

import android.util.Log;

/**
 * 
 * @author Luigi
 * 
 */
public class AndroidEventBusService implements EventBusService {

	private final static String TAG = "AndroidEventBusService";

	protected XMPPConnection connection;

	protected ConcurrentMap<String, MultiUserChat> topicMap;

	private EBusServiceStateListener listener;

	protected String host;

	protected Integer port;

	private AtomicReference<EBusServiceState> connectionState = new AtomicReference<EBusServiceState>(
			EBusServiceState.INACTIVE);

	private Random random;

	public XMPPConnection getConnection() {
		return connection;
	}

	@Override
	public void init(String host, int port,
			final EBusServiceStateListener listener) throws Exception {
		BasicLogger.writeLog(TAG, "init");
		random = new Random();
		this.host = host;
		this.port = port;
		this.listener = listener;
		topicMap = new ConcurrentHashMap<String, MultiUserChat>();
		ConnectionConfiguration config = new ConnectionConfiguration(host, port);
		config.setReconnectionAllowed(true);

		config.setDebuggerEnabled(false);
		connection = new XMPPConnection(config);
		SmackConfiguration.setKeepAliveInterval(3000);
		SmackConfiguration.setPacketReplyTimeout(3000);
		XMPPConnection.addConnectionCreationListener(connCreationListener);

	}

	@Override
	public void reset() throws Exception {
		// boolean stopped = stop();
		// if (stoppe d)
		// start();
		stop();
		start();

	}

	@Override
	public void destroy() {
		try {
			stop();
		} catch (Exception e) {
			BasicLogger.writeLog(TAG, "Failed to stop connection to broker", e);
		}
		this.connection.removeConnectionListener(connectionListener);
		this.topicMap = null;
	}

	@Override
	public void login(String login) {
		if (connectionState.get().equals(EBusServiceState.ACTIVE)) {
			try {
				connection.login(login, login);

				BasicLogger.writeLog(TAG,
						"Is authenticated: " + connection.isAuthenticated());

				PacketFilter filter = new MessageTypeFilter(Type.chat);

				connection.addPacketListener(packetListener, filter);

				Roster roster = connection.getRoster();

				roster.addRosterListener(new RosterListener() {

					@Override
					public void presenceChanged(Presence arg0) {
						BasicLogger.writeLog(TAG, "presenceChanged");

					}

					@Override
					public void entriesUpdated(Collection<String> arg0) {
						BasicLogger.writeLog(TAG, "entriesUpdated");

					}

					@Override
					public void entriesDeleted(Collection<String> arg0) {
						BasicLogger.writeLog(TAG, "entriesDeleted");

					}

					@Override
					public void entriesAdded(Collection<String> arg0) {
						BasicLogger.writeLog(TAG, "entriesAdded");

					}
				});
				// connection.loginAnonymously();
			} catch (XMPPException e) {
				Log.e("XMPPClientt", "Error on login", e);
			}
		}
	}

	@Override
	public boolean subscribe(String topicID, final EBusMessageHandler handler,
			final Class messageType) {
		if (topicID != null && !topicID.isEmpty()) {

			// if (validTopic(topicID)) {

			MultiUserChat group = new MultiUserChat(this.connection, topicID);
			String clientId = SharedPreferenceUtil.getSharedPrefsValue(
					ConfigurationConstants.conf_client_id_key,
					"GUEST" + random.nextInt());

			try {
				group.join(clientId);
			} catch (XMPPException e) {
				Log.e(TAG, "Error in subscribe method", e);
				return false;
			}
			group.addMessageListener(new PacketListener() {

				@Override
				public void processPacket(Packet packet) {
					Message msg = (Message) packet;
					if (msg != null) {
						try {
							BasicLogger.writeLog(TAG, "JSON MESSAGE RECEIVED: "
									+ msg.getBody());
							handler.onMessage(MessageConverter.convert(
									messageType, msg));
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}

			});

			addTopic(topicID, group);

			return true;
			// }
		}
		return false;

	}

	private void addTopic(String topicID, MultiUserChat group) {
		topicMap.put(topicID, group);

	}

	private boolean validTopic(String topicID) {
		return getTopicByID(topicID) == null;
	}

	private boolean subscribed(String topicID) {
		return getTopicByID(topicID) != null;
	}

	private MultiUserChat getTopicByID(String topicID) {
		return topicMap.get(topicID);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean publish(String topicID,
			EBusMessage<? extends Serializable> msg) {

		if (subscribed(topicID)) {

			MultiUserChat group = getTopicByID(topicID);
			Message baseMessage = group.createMessage();
			try {
				// group.sendMessage(MessageConverter.convert(
				// (EBusMessage<Serializable>) msg, baseMessage));
				group.sendMessage(MessageConverter.convert(msg, baseMessage));
			} catch (XMPPException e) {
				Log.e(TAG, "Error while publishing message!", e);
				return false;
			}
			return true;
		} else {
			BasicLogger.writeLog(TAG, "User is not subscribed to: " + topicID);

			for (String topic : topicMap.keySet()) {
				BasicLogger.writeLog(TAG, "" + topic);
			}
		}
		return false;
	}

	private ConnectionListener connectionListener = new ConnectionListener() {

		@Override
		public void reconnectionSuccessful() {
			BasicLogger.writeLog(TAG,
					"addConnectionListener: reconnectionSuccessful");
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.ACTIVE);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

		}

		@Override
		public void reconnectionFailed(Exception arg0) {
			BasicLogger.writeLog(TAG,
					"addConnectionListener: reconnectionFailed");
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.INACTIVE);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

		}

		@Override
		public void reconnectingIn(int arg0) {
			BasicLogger.writeLog(TAG, "addConnectionListener: reconnectingIn: "
					+ arg0);
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.ACTIVATING);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

		}

		@Override
		public void connectionClosedOnError(Exception arg0) {
			BasicLogger.writeLog(TAG,
					"addConnectionListener: connectionClosedOnError: " + arg0);
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.INACTIVE);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

		}

		@Override
		public void connectionClosed() {
			BasicLogger
					.writeLog(TAG, "addConnectionListener: connectionClosed");
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.INACTIVE);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

		}
	};

	private PacketListener packetListener = new PacketListener() {

		@Override
		public void processPacket(Packet packet) {
			BasicLogger.writeLog("addPacketListener", "Packet received: "
					+ packet);
			Message message = (Message) packet;

			String fromName = StringUtils.parseBareAddress(message.getFrom());
			Log.i("XMPPClient", "Got text [" + message.getBody() + "] from ["
					+ fromName + "]");

		}

	};

	private ConnectionCreationListener connCreationListener = new ConnectionCreationListener() {

		@Override
		public void connectionCreated(XMPPConnection connection) {
			BasicLogger.writeLog(TAG,
					"connectionCreated - OnLine With Remote Host");
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.ACTIVE);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}
			connection.addConnectionListener(new ConnectionListener() {

				@Override
				public void reconnectionSuccessful() {
					BasicLogger.writeLog(TAG,
							"Successfully reconnected to the XMPP server.");
				}

				@Override
				public void reconnectionFailed(Exception arg0) {
					BasicLogger.writeLog(TAG,
							"Failed to reconnect to the XMPP server.");
				}

				@Override
				public void reconnectingIn(int seconds) {
					BasicLogger.writeLog(TAG, "Reconnecting in " + seconds
							+ " seconds.");
				}

				@Override
				public void connectionClosedOnError(Exception arg0) {
					BasicLogger.writeLog(TAG,
							"Connection to XMPP server was lost.");
				}

				@Override
				public void connectionClosed() {
					BasicLogger.writeLog(TAG, "XMPP connection was closed.");
				}
			});
		};
	};

	@Override
	public boolean start() throws Exception {
		boolean connected = connection.isConnected();
		if (!connected) {
			EBusServiceState oldState = connectionState
					.getAndSet(EBusServiceState.ACTIVATING);
			if (AndroidEventBusService.this.listener != null) {
				BasicLogger.writeLog(TAG, "connectionCreated: onStateChanged");
				AndroidEventBusService.this.listener.onStateChanged(oldState,
						connectionState.get());
			}

			connection.connect();
			connection.addConnectionListener(connectionListener);
			BasicLogger.writeLog(TAG,
					"Is authenticated: " + connection.isAuthenticated());
			return true;
		}
		return false;

	}

	@Override
	public boolean stop() throws Exception {
		if (connection != null) {
			boolean connected = connection.isConnected();
			boolean logged = connection.isAuthenticated();

			if (connected || logged) {

				connection.removePacketListener(packetListener);

				connection.disconnect();

				return true;
			}
		}
		return false;

	}
}
